﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Perovich.GameObjects.Attributes;
using Perovich.GameObjects.Interfaces;
using Perovich.GameObjects.Tools.Util;
using System.CodeDom;
using System.Collections.ObjectModel;
using Microsoft.Xna.Framework;
using Perovich.GameObjects.Helpers;
using Microsoft.Xna.Framework.Graphics;

namespace Perovich.GameObjects.Components
{
    [ComponentDependency(typeof(Position2D), typeof(Size2D))]
    [AutomaticImport("Perovich.GameObjects", false)]
    [AutomaticImport("System.Collections.ObjectModel", false)]
    [AutomaticImport("System.Collections.Generic", false)]
    [AutomaticImport("Microsoft.Xna.Framework", false)]
    [AutomaticImport("Microsoft.Xna.Framework", true)]
    [AutomaticImport("Microsoft.Xna.Framework.Graphics", false)]
    [AutomaticImport("Microsoft.Xna.Framework.Graphics", true)]
    [AutomaticInterface(typeof(ICollidable2D))]
    [AutomaticProperty(typeof(Collidable2D), "GenerateProperty")]
    [AutomaticProperty("IsPrecise", "Collidable2D.IsPrecise")]
    [AutomaticProperty("IsTransformed", "Collidable2D.IsTransformed")]
    [AutomaticProperty("CollisionTex", "Collidable2D.CollisionTex", IsReadOnly = true)]
    [AutomaticFunction(typeof(Collidable2D), "GenerateFunction1")]
    [AutomaticEvent(typeof(Collidable2D), "GenerateEvent")]
    [AutomaticProperty(typeof(Collidable2D), "GenerateProperty2")]
    [AutomaticFunction(typeof(Collidable2D), "GenerateFunction2")]
    [AutomaticFunction(typeof(Collidable2D), "GenerateFunction3")]
    [AutomaticFunction(typeof(Collidable2D), "GenerateFunction4")]
    [AutomaticFunction(typeof(Collidable2D), "GenerateFunction5")]
    [AutomaticFunction(typeof(Collidable2D), "GenerateFunction6")]
    [AutomaticFunction(typeof(Collidable2D), "GenerateFunction7")]
    public sealed class Collidable2D : DrawableEntityComponent, ICollidable2D
    {
#if WINDOWS
        public static AutoFunction GenerateFunction1(CodeExpression me)
        {
            var fun = new AutoFunction("CheckCollisions", typeof(void), new CodeParameterDeclarationExpression[] { }, MemberAttributes.Public | MemberAttributes.Final);
            fun.AddLine(new CodeExpressionStatement(new CodeMethodInvokeExpression(me,"CheckCollisions")));
            return fun;
        }

        public static AutoFunction GenerateFunction2(CodeExpression me)
        {
            var fparam = new CodeParameterDeclarationExpression(new CodeTypeReference("ICollidable", 1), "targets");
            fparam.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(System.ParamArrayAttribute))));
            var fun = new AutoFunction("AddCollisionTargets", typeof(void), new CodeParameterDeclarationExpression[] { fparam }, MemberAttributes.Public | MemberAttributes.Final);
            fun.AddLine(new CodeExpressionStatement(new CodeMethodInvokeExpression(me, "AddCollisionTargets", new CodeArgumentReferenceExpression("targets"))));
            return fun;
        }

        public static AutoFunction GenerateFunction3(CodeExpression me)
        {
            var fparam = new CodeParameterDeclarationExpression(new CodeTypeReference("IEnumerable`1[ICollidable]"), "targets");
            var fun = new AutoFunction("AddCollisionTargets", typeof(void), new CodeParameterDeclarationExpression[] { fparam }, MemberAttributes.Public | MemberAttributes.Final);
            fun.AddLine(new CodeExpressionStatement(new CodeMethodInvokeExpression(me, "AddCollisionTargets", new CodeArgumentReferenceExpression("targets"))));
            return fun;
        }

        public static AutoFunction GenerateFunction4(CodeExpression me)
        {
            var fparam = new CodeParameterDeclarationExpression(new CodeTypeReference("IEntity", 1), "targets");
            fparam.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(System.ParamArrayAttribute))));
            var fun = new AutoFunction("AddCollisionTargets", typeof(void), new CodeParameterDeclarationExpression[] { fparam }, MemberAttributes.Public | MemberAttributes.Final);
            fun.AddLine(new CodeExpressionStatement(new CodeMethodInvokeExpression(me, "AddCollisionTargets", new CodeArgumentReferenceExpression("targets"))));
            return fun;
        }

        public static AutoFunction GenerateFunction5(CodeExpression me)
        {
            var fparam = new CodeParameterDeclarationExpression(new CodeTypeReference("IEnumerable`1[IEntity]"), "targets");
            var fun = new AutoFunction("AddCollisionTargets", typeof(void), new CodeParameterDeclarationExpression[] { fparam }, MemberAttributes.Public | MemberAttributes.Final);
            fun.AddLine(new CodeExpressionStatement(new CodeMethodInvokeExpression(me, "AddCollisionTargets", new CodeArgumentReferenceExpression("targets"))));
            return fun;
        }

        public static AutoFunction GenerateFunction6(CodeExpression me)
        {
            var fparam = new CodeParameterDeclarationExpression(new CodeTypeReference("BoundingBox", 1), "targets");
            fparam.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(System.ParamArrayAttribute))));
            var fun = new AutoFunction("AddCollisionTargets", typeof(void), new CodeParameterDeclarationExpression[] { fparam }, MemberAttributes.Public | MemberAttributes.Final);
            fun.AddLine(new CodeExpressionStatement(new CodeMethodInvokeExpression(me, "AddCollisionTargets", new CodeArgumentReferenceExpression("targets"))));
            return fun;
        }

        public static AutoFunction GenerateFunction7(CodeExpression me)
        {
            var fparam = new CodeParameterDeclarationExpression(new CodeTypeReference("IEnumerable`1[BoundingBox]"), "targets");
            var fun = new AutoFunction("AddCollisionTargets", typeof(void), new CodeParameterDeclarationExpression[] { fparam }, MemberAttributes.Public | MemberAttributes.Final);
            fun.AddLine(new CodeExpressionStatement(new CodeMethodInvokeExpression(me, "AddCollisionTargets", new CodeArgumentReferenceExpression("targets"))));
            return fun;
        }

        public static AutoEvent GenerateEvent(CodeExpression me)
        {
            var eve = new AutoEvent("Collided", "CollisionEventArgs", new CodeEventReferenceExpression(me, "Collided"));

            return eve;
        }

        public static AutoProperty GenerateProperty(CodeExpression me)
        {
            var prop = new AutoProperty("CollisionSolid", "dynamic", new CodePropertyReferenceExpression(me, "CollisionSolid"));
            prop.IsReadOnly = true;
            return prop;
        }

        public static AutoProperty GenerateProperty2(CodeExpression me)
        {
            var target = new CodePropertyReferenceExpression(me, "CollisionTargets");
            var prop = new AutoProperty("CollisionTargets", "ReadOnlyCollection`1[Object]", target);
            prop.IsReadOnly = true;
            return prop;
        }

#endif

        Texture t;
        Position2D pos;
        Size2D sz;

        public Collidable2D()
        {
            IsPrecise = false;
            IsTransformed = false;
        }

        public override void Initialize()
        {
            Owner.ComponentAdded += new EventHandler<EntityComponentEventArgs>(Owner_ComponentAdded);
            Owner.ComponentRemoved += new EventHandler<EntityComponentRemovedEventArgs>(Owner_ComponentRemoved);
            t = Owner.Find<Texture>();
            pos = Owner.Find<Position2D>();
            sz = Owner.Find<Size2D>();
            base.Initialize();
        }

        void Owner_ComponentRemoved(object sender, EntityComponentRemovedEventArgs e)
        {
            if (e.Component is Texture)
                t = null;
        }

        void Owner_ComponentAdded(object sender, EntityComponentEventArgs e)
        {
            Texture c = e.Component as Texture;
            if (c != null)
                t = c;
        }


        private List<object> collisionTargets = new List<object>();
        public ReadOnlyCollection<object> CollisionTargets
        {
            get
            {
                return new ReadOnlyCollection<object>(collisionTargets);
            }
        }

        public event EventHandler<CollisionEventArgs> Collided;


        public void AddCollisionTargets(params ICollidable[] targets)
        {
            if (targets == null)
                throw new ArgumentNullException("targets");
            foreach (ICollidable obj in targets)
            {
                collisionTargets.Add(obj);
            }
        }

        public void AddCollisionTargets(IEnumerable<ICollidable> targets)
        {
            if (targets == null)
                throw new ArgumentNullException("targets");
            foreach (ICollidable obj in targets)
            {
                collisionTargets.Add(obj);
            }
        }

        public void AddCollisionTargets(params IEntity[] targets)
        {
            ICollidable[] a;
            if ((a = targets as ICollidable[]) != null)
                AddCollisionTargets(a);
            if (targets == null)
                throw new ArgumentNullException("targets");
            foreach (IEntity obj in targets)
            {
                collisionTargets.Add(obj);
            }
        }

        public void AddCollisionTargets(IEnumerable<IEntity> targets)
        {
            if (targets == null)
                throw new ArgumentNullException("targets");
            foreach (IEntity obj in targets)
            {
                collisionTargets.Add(obj);
            }
        }

        public void AddCollisionTargets(params BoundingBox[] targets)
        {
            if (targets == null)
                throw new ArgumentNullException("targets");
            foreach (BoundingBox obj in targets)
            {
                collisionTargets.Add(obj);
            }
        }

        public void AddCollisionTargets(IEnumerable<BoundingBox> targets)
        {
            if (targets == null)
                throw new ArgumentNullException("targets");
            foreach (BoundingBox obj in targets)
            {
                collisionTargets.Add(obj);
            }
        }

        public void CheckCollisions()
        {
            if ((this.Enabled) && (Owner.Enabled))
            {
                List<object> to_remove = new List<object>();
                foreach (object other in CollisionTargets)
                {
                    ICollidable col = other as ICollidable;
                    IEntity ent = other as IEntity;
                    BoundingBox? b = other as BoundingBox?;
                    if (col != null)
                    {
                        if (CollisionSolid.Intersects(col.CollisionSolid))
                        {
                            SignalCollision(col);
                        }
                    }
                    else if (ent != null)
                    {
                        Collidable2D comp = ent.Find<Collidable2D>();
                        if (comp != null)
                        {
                            if (CollisionSolid.Intersects(comp.CollisionSolid))
                            {
                                SignalCollision(comp);
                            }
                        }
                        else
                        {
                            to_remove.Add(ent);
                        }
                    }
                    else if (b != null)
                    {
                        BoundingBox merged = BoundingBox.CreateMerged(CollisionSolid,b.Value);
                        if (merged != b.Value)
                        {
                            SignalCollision(b.Value);
                        }
                    }
                }

                foreach (var tr in to_remove)
                {
                    collisionTargets.Remove(tr);
                }
            }
        }

        private void SignalCollision(BoundingBox other)
        {
            if ((!IsPrecise) || PixelPerfectBounding(other))
            {
                if (Collided != null)
                {
                    Collided(this, new CollisionEventArgs(other));
                }
            }
        }

        private bool PixelPerfectBounding(BoundingBox other)
        {
            int t2 = (int)Math.Max(this.CollisionSolid.Top(), other.Top());
            int b2 = (int)Math.Min(this.CollisionSolid.Bottom(), other.Bottom());
            int l2 = (int)Math.Max(this.CollisionSolid.Left(), other.Left());
            int r2 = (int)Math.Min(this.CollisionSolid.Right(), other.Right());

            int t1 = this.CollisionSolid.Top();
            int b1 = this.CollisionSolid.Bottom();
            int l1 = this.CollisionSolid.Left();
            int r1 = this.CollisionSolid.Right();




            int left = (l1 < l2) ? l1 : r2;
            int right = (r1 > r2) ? r1 : l2;
            int top = (t1 < t2) ? t1 : b2;
            int bottom = (b1 > b2) ? b1 : t2;
            if ((t1 == t2) && (b1 == b2) && (l1 == l2) && (r1 == r2))
            {
                bottom = top;
                right = left;
            }


            var dataA = Textures.GetCollisionData(t.Tex);

            // Check every point within the intersection bounds

            //Check the top and bottom
            for (int y = top; y < bottom; y++)
            {
                for (int x = l1; x < r1; x++)
                {
                    // Get the color of both pixels at this point
                    Color colorA = dataA[(x - this.CollisionSolid.Left()) +
                                (y - this.CollisionSolid.Top()) * t.Tex.Width];

                    // If both pixels are not completely transparent,
                    if (colorA.A != 0)
                    {
                        // then an intersection has been found
                        return true;
                    }
                }
            }


            //Check the left and right
            for (int y = t1; y < b2; y++)
            {
                for (int x = left; x < right; x++)
                {
                    // Get the color of both pixels at this point
                    Color colorA = dataA[(x - this.CollisionSolid.Left()) +
                                (y - this.CollisionSolid.Top()) * t.Tex.Width];

                    // If both pixels are not completely transparent,
                    if (colorA.A != 0)
                    {
                        // then an intersection has been found
                        return true;
                    }
                }
            }

            // No intersection found
            return false;
        }


        private void SignalCollision(ICollidable other)
        {
            if ((!IsPrecise) || PixelPerfectCollision(other))
            {
                if (Collided != null)
                {
                    var e = (other as EntityComponent);
                    if (e == null)
                    {
                        Collided(this.Owner, new CollisionEventArgs(other));
                    }
                    else
                    {
                        Collided(this.Owner, new CollisionEventArgs(e.Owner));
                    }
                }
            }
        }

        private bool PixelPerfectCollision(ICollidable other2)
        {
            ICollidable2D other = other2 as ICollidable2D;
            if (other == null) return true;
            int top = (int)Math.Max(this.CollisionSolid.Top(), other.CollisionSolid.Top());
            int bottom = (int)Math.Min(this.CollisionSolid.Bottom(), other.CollisionSolid.Bottom());
            int left = (int)Math.Max(this.CollisionSolid.Left(), other.CollisionSolid.Left());
            int right = (int)Math.Min(this.CollisionSolid.Right(), other.CollisionSolid.Right());

            var dataA = Textures.GetCollisionData(t.Tex);
            var dataB = Textures.GetCollisionData(other.CollisionTex);

            // Check every point within the intersection bounds
            for (int y = top; y < bottom; y++)
            {
                for (int x = left; x < right; x++)
                {
                    // Get the color of both pixels at this point
                    Color colorA = dataA[(x - this.CollisionSolid.Left()) +
                                (y - this.CollisionSolid.Top()) * this.CollisionSolid.Width()];
                    Color colorB = dataB[(x - other.CollisionSolid.Left()) +
                                (y - other.CollisionSolid.Top()) * other.CollisionSolid.Width()];

                    // If both pixels are not completely transparent,
                    if (colorA.A != 0 && colorB.A != 0)
                    {
                        // then an intersection has been found
                        return true;
                    }
                }
            }

            // No intersection found
            return false;
        }




        public Texture2D CollisionTex { get { return t.Tex; } }

        public dynamic CollisionSolid { get { return new BoundingBox(new Vector3(pos.X - sz.Origin.X, pos.Y - sz.Origin.Y, 0), new Vector3(pos.X - sz.Origin.X + sz.Width, pos.Y - sz.Origin.Y + sz.Height, 0)); } }
        
        public bool IsPrecise { get; set; }

        //Unused at this time
        public bool IsTransformed { get; set; }
    }
}
